"""
5.1 函数的定义
本小节学习如何定义一个最简单的函数。通过本小节的学习将掌握函数的基本语法，了
解抽象与代码复用的概念。
5.1.1 函数应用场景
本知识点主要学习函数的应用场景，函数的常用应用场景是：
1.某些语句块所提供的功能，需要被反复使用；
2.某个任务较复杂，拆分成多个简单的问题，将这些问题的解决方案通过函数组织在
一起。
例如，求列表长度是一个常见的功能，因此，Python 将求列表长度所使用的语句块定义
为一个函数，函数名为 len，当用户需要使用该功能时，直接调用这个函数就好了。
又如，对于统计班级平均成绩并统计成绩超过平均分的学生信息这个任务，可以先将它
分解为如下三个过程：录入学生信息及对应分数、计算班级平均分、筛选成绩超过平均分的
学生信息。如此一来，在对每个过程进行编码时，可以不关注其余过程，而只专注于当前过
程。所有过程编码完毕后，只需按照需求调用所编写的过程函数即可。当任务需求发生变化
（例如，统计成绩未达到平均分的学生信息），则只需要修改对应的过程函数。
5.1.2 函数的定义与调用
本知识点主要学习函数的基本定义语法和调用方式，一种最简单的函数定义语法格式如
下：
def 函数名():
函数体
其中，函数名命名规范与变量名命名规范一致，以字母或下划线开头，后面字符由字母、
数字、下划线（_）组成，且不能以数字开头，不能使用 Python 关键字（如 for、if 等）。一
般，函数的命名需要顾名思义，能够通过函数名体现函数的简要功能。
通过上述方式定义的函数调用的语法格式如下：
函数名()
调用函数，只需要在函数名后加小括号，即可运行函数对应的函数体。
"""
"""
定义和调用函数
"""
# 以下代码为定义一个函数
def hello():
    print('hello world!')

# 以下代码为调用已定义的函数
hello()
print("*" * 100)

"""
5.2 函数的参数
本知识点主要学习函数的参数。在定义函数时，可以为函数设置参数，根据参数值的不
同，函数的运行结果可能不同。在 Python 语言中，函数的参数可以是任何类型，包括但不
限于数字型、字符串、列表、元组、字典、集合。在定义函数参数时，不需要指定参数的类
型。
5.2.1 位置参数
本知识点学习函数中位置参数的使用以及函数参数的传值机制，定义接收位置参数的函
数语法格式如下：
def 函数名(参数名 1, 参数名 2, … ,参数 N):
函数体
调用上述函数的语法格式如下：
函数名(参数值 1, 参数值 2, …,参数 N)
一个函数可以有多个参数，在定义函数时，函数名后的括号中就是函数所需的参数名。
这些参数名看上去就像是一个个的变量，在函数的语句块内部，可以将这些参数作为已知的
变量来使用，变量的值由用户调用时传入。
调用接收位置参数的函数，仍然通过函数名，函数名后的括号中就是调用函数时所传入
的参数值，这些参数值与定义函数时定义好的参数名一一对应。
"""
"""
练习
"""
# 实现 1 + 2 = 3的功能
def plus(item1, item2):
    print(f'{item1} + {item2} 的结果为 {item1 + item2}')
# 这里需要强调的是，函数在未调用前，是不会被程序执行的，只有再被调用后，函数才会执行
# 所以函数的执行顺讯，受调用顺序控制
plus(1,2)
print('*' * 100)

#创建一个学生信息（包括姓名，年龄，身高，体重）
def create_student(name, age, height, weight):
    print('开始打印学生信息')
    print(f'该学生的姓名为: {name}, 年龄为: {age}, 身高为:{height}, 体重为: {weight}')

create_student('张三', 20, 180, 90)
print('*' * 100)


def print_list(input_list):
    for item in input_list:
        print(f'item的值是:{item}')

print_list(['张三','李四','王五','赵六'])
print("*" * 100)

"""
5.2.2 默认参数
本知识点主要学习函数默认参数的使用，定义接收默认参数的函数的语法如下：
def 函数名(参数名=默认值):
函数体
习惯上，此处等号两边没有空格，调用接收默认参数的方式有两种：
1. 指定实参的值：此时调用函数的语法格式与位置参数一致，即通过函数名(实参) 调
用，这种形式下，形参的值即为实参的值，默认值不起作用；
2. 不指定实参的值：此时调用函数的语法格式为：函数名()，当不指定实参时，形参
的值即为默认值。
需要注意的是，函数可以有多个参数，在定义函数时，若同时存在位置参数与默认参数
时，需要将默认参数写在位置参数之后。
默认参数的使用，可以减少调用函数时的代码量，也可以降低使用该函数的难度。在定
义函数时，可以将一些不常用到的参数或者参数值在大部分时候都一样的参数定义为默认参
数，这样用户在调用函数时可以不用显式地传入该参数，减少代码量。
"""

def print_name(name='张三'):
    print(f'输入的姓名为:{name}')

print_name()
print_name('李四')
print('*' * 100)

"""
5.2.3 可变参数
本知识点学习可变参数的使用，函数可变参数的语法格式如下：
def 函数名(*参数):
函数体
其中，* 表示所修饰的形参是一个可变参数，用户可以传入任意数量的参数。用户所传入的
参数会被按照顺序包装成一个元组，即不论用户传入的参数是什么数据类型，函数可变参数对应
的形参的数据类型都是元组。
"""
# 函数作用：计算学生成绩的平均值
def get_average_score(*student_score_tuple):
    avg_sum = 0
    for score in student_score_tuple:
        avg_sum += score
    avg_sum = avg_sum / len(student_score_tuple)
    print(f'学生平均成绩是: {avg_sum}')

get_average_score(100)
get_average_score(100, 90)
get_average_score(100, 90, 80)
get_average_score(100, 90, 80,100, 90,100, 90,100, 90,100, 90)
print("*" * 100)

"""
5.2.4 关键字参数（参数信息不稳定）
本知识点学习函数关键字参数的使用。定义接收关键字参数的函数的语法格式如下：
def 函数名(**参数):
函数体
习惯上，参数的名字都使用 kw。
调用上述函数的语法格式是：
函数名(参数名=参数值, 参数名=参数值, … ,)
在函数内部，kw 是一个字典，它的内容是以参数名（字符串形式）为键，参数值为值
的一个字典。
"""
"""
练习：打印学生信息
"""
print('开始执行print_student_info函数')
def print_student_info(**kw):
    # 加上if判断，保证函数运行结果
    if 'name' not in kw or 'no' not in kw or 'height' not in kw:
        print('关键字不在输入参数kw中')
        return
    print(f'学生姓名: {kw["name"]}, 学号: {kw["no"]}, 身高:{kw["height"]}')


#正确写法
print_student_info(name='张三', no=123, height=180)
# 参数名不对产生的问题
# print_student_info(name1='张三', no=123, height=180)
print("*" * 100)

"""
5.2.5 命名关键字参数(参数稳定)
用户可以自由决定传入的关键字参数，开发者没有办法预测用户将会传入的参数。
Python 提供了另一种与关键字参数类似的函数参数：命名关键字参数。使用命名关键字可以
限制用户传入的参数，且强制要求用户指定参数名。
定义接收命名关键字参数的函数的语法如下：
def 函数名(*, 参数):
函数体
调用上述函数的语法是：函数名(参数名=参数值)。在调用接收命名关键字参数的函数
时，参数名必须指定，且必须是函数定义时所使用的命名关键字参数名。
"""
print("开始执行print_student_info2函数(命名关键字参数)")
def print_student_info2(*,name, no, height):
    print(f'学生姓名: {name}, 学号: {no}, 身高:{height}')
# 强制指定
print_student_info2(name='张三', no=123, height=180)
print("*" * 100)

"""
5.3 函数的返回值
本小节学习函数返回值的用法。函数返回值的功能是结束函数调用，并返回值。返回值
的使用可以使调用者获悉函数的执行情况，根据函数执行情况做后续的处理。另外，函数的
返回值还可以使函数更专注于功能的实现，而不关注结果的表现形式。
5.3.1 简单返回值
本知识点学习函数返回值的最基本用法。定义有返回值函数的语法如下：
def 函数名(参数):
语句块
return 返回值
调用有返回值的函数的语法是：变量=函数名(参数)。
带有返回值的函数的定义即在 5.1、5.2 节所述函数的基础上添加了 return 语句。带有返
回值的函数的调用同样可以直接使用函数名(参数)的形式调用。但是由于函数带有返回值，
因此，通过令一个变量等于函数调用的形式是更常用的形式，在函数被调用完成后（在带有
返回值的函数中，即第一条 return 语句被执行后），变量的值等于函数的返回值。
"""
"""
练习：定义一个获取最大值的函数
"""
def get_max_score(scores):
    #1. 将输入scores列表（元组）中的第一个值赋予max_score变量
    max_score = scores[0]
    #2. 遍历scores
    for score in scores:
        #3. 判断遍历的当前score内容是否大于max_score, 如果大于的话就覆盖max_score
        max_score = score if score > max_score else max_score
    print(f'max_score在函数内的类型是: {type(max_score)}')
    return max_score

"""
练习：定义获取最小值
"""
def get_min_score(scores):
     #1. 将输入scores列表（元组）中的第一个值赋予max_score变量
    min_score = scores[0]
    #2. 遍历scores
    for score in scores:
        #3. 判断遍历的当前score内容是否大于max_score, 如果大于的话就覆盖max_score
        min_score = score if score < min_score else min_score
    return min_score


#4. 调用get_max_score
scores = (70, 80, 90, 95, 100)
# 这里定义为my_max_score，保证程序中不要用重复定义变量的情况
my_max_score = get_max_score(scores=scores)
my_min_score = get_min_score(scores)
print(f"输入可迭代对象: {scores}, 最大值是: {my_max_score}")
print(f'max_score在函数外的类型是: {type(my_max_score)}')

print(f"输入可迭代对象: {scores}, 最小值是: {my_min_score}")

"""
5.3.2 多返回语句
本知识点学习有多条 return 语句是函数的执行机制。return 的功能包括两个部分：终止
函数运行和返回函数的返回值。当函数被调用后，函数将会在第一次执行完 return 语句后结
束，返回对应的值，即使所执行的 return 之后还有语句。
"""
"""
练习：通过函数返回可迭代对象的最大值、最小值和平均值
"""
print("*" * 100)
print('开始执行获取最大值、最小值和平均值')
def get_max_min_and_avg(scores):
    # max() 获取scores中的最大值
    # min() 获取scores中的最小值
    # sum() 将scores中的内容进行累加
    # len() 获取scores的长度
    return max(scores), min(scores), sum(scores) / len(scores)

scores = [70, 80, 90, 100]
res = get_max_min_and_avg(scores=scores)
# 完全解包
my_max, my_min, my_avg = get_max_min_and_avg(scores)
# 部分解包.....
print(f'返回结果是: {res}')
print(f'res的类型是{type(res)}')
print(f'my_max: {my_max}, my_min: {my_min}, my_avg: {my_avg}')





